/* ----------------------------------------------------------------------------
* File: service.cpp
*
* Desc: Windows service stuffs
*
* Created: 20/12/2010, by Alexey Korshunov
* ---------------------------------------------------------------------------- */
#include "main.h"

using namespace SKYPE4COMLib;
using namespace std;

SkypeEnv Env;
LPWSTR serviceName = TEXT("Skype Plugin");


/************************************************************************/
/* Create Skype service                                                 */
/************************************************************************/
static int CreateSkypeService(SC_HANDLE hSCManager, fstream &LogFile)
{
	TCHAR szPath[MAX_PATH];
	DWORD dwErr;

	// Get file name
	if (!GetModuleFileName(NULL, szPath, MAX_PATH))
	{
		LogErrorFunction("GetModuleFileName", LogFile);
		LogMessage("skype_plugin: GetModuleFileName failed", LogFile);
#ifdef _DEBUG
		OutputDebugString(TEXT("skype_plugin: InstallSkypeService() ERROR - Unable to get module name"));
#endif
		return SKYPE_PLUG_ERROR;
	}

	// Create service
	SC_HANDLE hService = CreateService(
		hSCManager,					// SCManager database 
		serviceName,				// name of service 
		serviceName,           // service name to display 
		SERVICE_ALL_ACCESS,        // desired access 
		SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, // service type 
		SERVICE_DEMAND_START,      // start type 
		SERVICE_ERROR_NORMAL,      // error control type 
		szPath,                    // path to service's binary 
		NULL,                      // no load ordering group 
		NULL,                      // no tag identifier 
		NULL,                      // no dependencies 
		NULL,                      // LocalSystem account 
		NULL);                     // no password 

	if (hService == NULL)
	{
		dwErr = GetLastError();
		if (dwErr == ERROR_SERVICE_EXISTS)
		{
			LogMessage("skype_plugin: Service already exists", LogFile);
#ifdef _DEBUG
			OutputDebugString(TEXT("skype_plugin: InstallSkypeService() - Service already exists"));
#endif
		}
		else
		{
			LogErrorFunction("skype_plugin: CreateSkypeService - CreateService()", LogFile);
			LogMessage("skype_plugin: ERROR - Unable to create service", LogFile);
#ifdef _DEBUG
			OutputDebugString(TEXT("skype_plugin: InstallSkypeService() ERROR - Unable to create service"));
#endif
			return SKYPE_PLUG_ERROR;
		}
	}

	CloseServiceHandle(hService);
	return SKYPE_PLUG_OK;
}


/************************************************************************/
/* Install our service                                                  */
/************************************************************************/
int InstallWinService(TCHAR *servicePath)
{
	int ret = SKYPE_PLUG_OK;
	WCHAR logFileName[512];
	fstream Log;

#ifdef _DEBUG
	OutputDebugString(TEXT("skype_plugin: InstallSkypeService() - begin"));
#endif

	// open log file
	wsprintf(logFileName, TEXT("%s.log"), servicePath);
	Log.open(logFileName, ios::out);

	// connection to the SCM (Service Control Manager)
	SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
	if (hSCManager == NULL)
	{
		LogErrorFunction("skype_plugin: InstallService - OpenSCManager()", Log);
		LogMessage("skype_plugin: ERROR - Unable to connect to the SCM", Log);
		if (Log.is_open())
			Log.close();
#ifdef _DEBUG
		OutputDebugString(TEXT("skype_plugin: InstallSkypeService() ERROR - Unable to connect to the SCM"));
#endif
		return SKYPE_PLUG_ERROR;
	}

	// Create Skype plugin as windows service
	if (CreateSkypeService(hSCManager, Log) != SKYPE_PLUG_OK)
		ret = SKYPE_PLUG_ERROR;
	else
	{
		LogMessage("skype_plugin: Service installed successfully", Log);
#ifdef _DEBUG
		OutputDebugString(TEXT("skype_plugin: InstallSkypeService() - Service installed successfully"));
#endif
	}
	
	// close handles
	CloseServiceHandle(hSCManager);
	
	if (Log.is_open())
		Log.close();

#ifdef _DEBUG
		OutputDebugString(TEXT("skype_plugin: InstallSkypeService() - end"));
#endif
	return ret;
}


/************************************************************************/
/* Remove our service                                                   */
/************************************************************************/
int RemoveWinService(TCHAR *servicePath)
{
	int ret = SKYPE_PLUG_OK;
	WCHAR logFileName[512];
	fstream Log;

#ifdef _DEBUG
	OutputDebugString(TEXT("skype_plugin: RemoveSkypeService() - begin"));
#endif

	// open log file
	wsprintf(logFileName, TEXT("%s.log"), servicePath);
	Log.open(logFileName, ios::out);

	// connect to SCM
	SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	if (hSCManager == NULL)
	{
		LogErrorFunction("skype_plugin: RemoveService - OpenSCManager()", Log);
		LogMessage("skype_plugin: ERROR - Unable to connect to the SCM", Log);
		if (Log.is_open())
			Log.close();
#ifdef _DEBUG
		OutputDebugString(TEXT("skype_plugin: RemoveSkypeService() ERROR - Unable to connect to the SCM"));
#endif
		return SKYPE_PLUG_ERROR;
	}
	
	// open service
	SC_HANDLE hService = OpenService(hSCManager, serviceName, SERVICE_STOP | DELETE);
	if (hService == NULL)
	{
		LogErrorFunction("skype_plugin: RemoveService - OpenService()", Log);
		LogMessage("skype_plugin: ERROR - Can't open service", Log);
		CloseServiceHandle(hSCManager);
		if (Log.is_open())
			Log.close();
#ifdef _DEBUG
		OutputDebugString(TEXT("skype_plugin: RemoveSkypeService() ERROR - Can't open service"));
#endif
		return SKYPE_PLUG_ERROR;
	}

	// delete service
	if (!DeleteService(hService))
	{
		LogErrorFunction("skype_plugin: RemoveService - OpenService()", Log);
		LogMessage("skype_plugin: ERROR - Can't remove service", Log);
#ifdef _DEBUG
		OutputDebugString(TEXT("skype_plugin: RemoveSkypeService() ERROR - Can't remove service"));
#endif
		ret = SKYPE_PLUG_ERROR;
	}
	else
	{
		LogMessage("skype_plugin: Service removed successfully", Log);
#ifdef _DEBUG
		OutputDebugString(TEXT("skype_plugin: RemoveSkypeService() - Service removed successfully"));
#endif
	}

	// close handles
	CloseServiceHandle(hService);
	CloseServiceHandle(hSCManager);

	if (Log.is_open())
		Log.close();

#ifdef _DEBUG
		OutputDebugString(TEXT("skype_plugin: RemoveSkypeService() - end"));
#endif
	return ret;
}


/************************************************************************/
/* Start Skype plugin as service                                        */
/************************************************************************/
static int StartSkypeService(SC_HANDLE hSCManager, fstream &Log)
{
	SERVICE_STATUS_PROCESS ssStatusProcess;
	SERVICE_STATUS serviceStatus;
	DWORD dwOldCheckPoint;
	DWORD dwStartTickCount;
	DWORD dwWaitTime;
	DWORD dwBytesNeeded;

	// open service
	SC_HANDLE hService = OpenService(hSCManager, serviceName, SERVICE_ALL_ACCESS);
	if (hService == NULL)
	{
		LogErrorFunction("skype_plugin: OpenService()", Log);
		LogMessage("skype_plugin: ERROR - Can't open service", Log);
		OutputDebugString(TEXT("skype_plugin: StartWinService() ERROR - Can't open service"));
		return SKYPE_PLUG_ERROR;
	}

	// start service
	if(!StartService(hService, 0, NULL)) 
	{
		DWORD dwErr = GetLastError();
		switch(dwErr) 
		{
		case ERROR_SERVICE_ALREADY_RUNNING:
			break;

		case ERROR_FILE_NOT_FOUND:
			LogMessage("Error: ERROR_FILE_NOT_FOUND", Log);
			OutputDebugString(TEXT("skype_plugin: StartWinService() ERROR_FILE_NOT_FOUND"));
			break;

		case ERROR_ACCESS_DENIED:
			LogMessage("Error: ERROR_ACCESS_DENIED", Log);
			OutputDebugString(TEXT("skype_plugin: StartWinService() ERROR_ACCESS_DENIED"));
			break;

		case ERROR_CIRCULAR_DEPENDENCY:
			LogMessage("Error: ERROR_CIRCULAR_DEPENDENCY", Log);
			OutputDebugString(TEXT("skype_plugin: StartWinService() ERROR_CIRCULAR_DEPENDENCY"));
			break;

		case ERROR_DUPLICATE_SERVICE_NAME:
			LogMessage("Error: ERROR_DUPLICATE_SERVICE_NAME", Log);
			OutputDebugString(TEXT("skype_plugin: StartWinService() ERROR_DUPLICATE_SERVICE_NAME"));
			break;

		case ERROR_INVALID_HANDLE:
			LogMessage("Error: ERROR_INVALID_HANDLE", Log);
			OutputDebugString(TEXT("skype_plugin: StartWinService() ERROR_INVALID_HANDLE"));

			break;
		case ERROR_INVALID_NAME:
			LogMessage("Error: ERROR_INVALID_NAME", Log);
			OutputDebugString(TEXT("skype_plugin: StartWinService() ERROR_INVALID_NAME"));
			break;

		case ERROR_INVALID_PARAMETER:
			LogMessage("Error: ERROR_INVALID_PARAMETER", Log);
			OutputDebugString(TEXT("skype_plugin: StartWinService() ERROR_INVALID_PARAMETER"));
			break;

		case ERROR_INVALID_SERVICE_ACCOUNT:
			LogMessage("Error: ERROR_INVALID_SERVICE_ACCOUNT", Log);
			OutputDebugString(TEXT("skype_plugin: StartWinService() ERROR_INVALID_SERVICE_ACCOUNT"));
			break;

		case ERROR_SERVICE_EXISTS:
			LogMessage("Error: ERROR_SERVICE_EXISTS", Log);
			OutputDebugString(TEXT("skype_plugin: StartWinService() ERROR_SERVICE_EXISTS"));
			break;

		default:
			LogMessage("Error: Undefined", Log);
			OutputDebugString(TEXT("skype_plugin: StartWinService() Undefined"));
			break;
		}

		LogErrorFunction("skype_plugin: StartService - StartService()", Log);
		LogMessage("skype_plugin: ERROR - Can't start service", Log);
		OutputDebugString(TEXT("skype_plugin: StartWinService() ERROR - Can't start service"));

		return SKYPE_PLUG_ERROR;
	}

	// Check the status until the service is no longer start pending
	if (!QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssStatusProcess, sizeof(SERVICE_STATUS_PROCESS), &dwBytesNeeded ))
	{
		LogMessage("skype_plugin: StartWinService() - Unable to query service status", Log);
		OutputDebugString(TEXT("skype_plugin: StartWinService() - Unable to query service status"));
		return SKYPE_PLUG_ERROR;
	}

	// Save the tick count and initial checkpoint
	dwStartTickCount = GetTickCount();
	dwOldCheckPoint = ssStatusProcess.dwCheckPoint;

	while(ssStatusProcess.dwCurrentState == SERVICE_START_PENDING)
	{
		// Do not wait longer than the wait hint. A good interval is 
		// one tenth the wait hint, but no less than 1 second and no 
		// more than 10 seconds. 

		dwWaitTime = ssStatusProcess.dwWaitHint / 10;

		if( dwWaitTime < 1000 )
			dwWaitTime = 1000;
		else if ( dwWaitTime > 10000 )
			dwWaitTime = 10000;

		Sleep(dwWaitTime);

		// Check the status again. 
		if (!QueryServiceStatusEx( 
			hService,             // handle to service 
			SC_STATUS_PROCESS_INFO, // info level
			(LPBYTE)&ssStatusProcess,              // address of structure
			sizeof(SERVICE_STATUS_PROCESS), // size of structure
			&dwBytesNeeded ) )              // if buffer too small
			break; 

		if (ssStatusProcess.dwCheckPoint > dwOldCheckPoint)
		{
			// The service is making progress.
			dwStartTickCount = GetTickCount();
			dwOldCheckPoint = ssStatusProcess.dwCheckPoint;
		}
		else
		{
			if(GetTickCount() - dwStartTickCount > ssStatusProcess.dwWaitHint)
			{
				// No progress made within the wait hint
				break;
			}
		}
	}

	CloseServiceHandle(hService);

	if (ssStatusProcess.dwCurrentState == SERVICE_RUNNING)
	{
		LogMessage("skype_plugin: Service started successfully", Log);
		OutputDebugString(TEXT("skype_plugin: StartWinService() - Service started successfully"));
		return SKYPE_PLUG_OK;
	}
	else
	{
		LogMessage("skype_plugin: ERROR - Service not started", Log);
		OutputDebugString(TEXT("skype_plugin: ERROR - Service not started"));

		return SKYPE_PLUG_ERROR;
	}
}


/************************************************************************/
/* Start our service                                                    */
/************************************************************************/
int StartWinService(TCHAR *servicePath)
{
	int ret = SKYPE_PLUG_OK;
	WCHAR logFileName[512];
	fstream Log;

#ifdef _DEBUG
	OutputDebugString(TEXT("skype_plugin: StartSkypeService() - begin"));
#endif

	// open log file
	wsprintf(logFileName, TEXT("%s.log"), servicePath);
	Log.open(logFileName, ios::out);

	// connection to the SCM (Service Control Manager)
	SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	if (hSCManager == NULL)
	{
		LogErrorFunction("skype_plugin: StartService - OpenSCManager()", Log);
		LogMessage("skype_plugin: ERROR - Unable to connect to the SCM", Log);
		if (Log.is_open())
			Log.close();
#ifdef _DEBUG
		OutputDebugString(TEXT("skype_plugin: StartSkypeService() ERROR - Unable to connect to the SCM"));
#endif
		return SKYPE_PLUG_ERROR;
	}

	// Try to start service
	ret = StartSkypeService(hSCManager, Log);
	
	CloseServiceHandle(hSCManager);

	if (Log.is_open())
		Log.close();

#ifdef _DEBUG
	OutputDebugString(TEXT("skype_plugin: StartSkypeService() - end"));
#endif

	return ret;
}


/************************************************************************/
/* Stop our service                                                    */
/************************************************************************/
int StopWinService(TCHAR *servicePath)
{
	return SKYPE_PLUG_OK;
}


/************************************************************************/
/* Handler for control status requests                                  */
/************************************************************************/
void ControlHandler(DWORD request)
{
	switch (request)
	{
	case SERVICE_CONTROL_STOP:
		LogMessage("skype_plugin: Stopped", Env.logFile);
		Env.serviceStatus.dwCurrentState = SERVICE_STOPPED;
		Env.serviceStatus.dwWin32ExitCode = 0;
		break;

	case SERVICE_CONTROL_SHUTDOWN:
		LogMessage("skype_plugin: Shutdown", Env.logFile);
		Env.serviceStatus.dwCurrentState = SERVICE_STOPPED;
		Env.serviceStatus.dwWin32ExitCode = 0;
		break;

	default:
		break;
	}

	SetServiceStatus(Env.hStatus, &Env.serviceStatus);

	return;
}


/************************************************************************/
/* Main function for service                                            */
/************************************************************************/
void ServiceMain(int argc, char** argv)
{
	HRESULT hr = S_OK;
	char logFileName[512];
	HANDLE hThreads[COUNT_THREADS];

	OutputDebugString(TEXT("skype_plugin: ServiceMain() - begin"));

	// Open log file
	sprintf(logFileName, "%s.log", argv[0]);
	Env.logFile.open(logFileName, ios::out);
	LogMessage("ServiceMain() - start", Env.logFile);

	Env.serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
	Env.serviceStatus.dwCurrentState = SERVICE_START_PENDING;
	Env.serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
	Env.serviceStatus.dwWin32ExitCode = 0;
	Env.serviceStatus.dwServiceSpecificExitCode = 0;
	Env.serviceStatus.dwCheckPoint = 0;
	Env.serviceStatus.dwWaitHint = 0;

	// register handler for controlling requests
	Env.hStatus = RegisterServiceCtrlHandler(serviceName, (LPHANDLER_FUNCTION)ControlHandler);
	if (Env.hStatus == (SERVICE_STATUS_HANDLE)0)
	{
		LogMessage("skype_plugin: ERROR - Unable to register control handler", Env.logFile);
		OutputDebugString(TEXT("skype_plugin: ServiceMain() ERROR - Can't register control handler"));
		return;
	}

	LogMessage("skype_plugin: Control handler is registered", Env.logFile);
	OutputDebugString(TEXT("skype_plugin: ServiceMain() - Control handler is registered"));

	// Report running status
	Env.serviceStatus.dwCurrentState = SERVICE_RUNNING;
	Env.serviceStatus.dwCheckPoint = 0;
	Env.serviceStatus.dwWaitHint = 0;
	SetServiceStatus(Env.hStatus, &Env.serviceStatus);

	// Init Skype plugin
	if (InitSkypePlugin(&Env, hThreads) != SKYPE_PLUG_OK)
	{
		// stop service
		Env.serviceStatus.dwCurrentState = SERVICE_STOPPED;
		Env.serviceStatus.dwWin32ExitCode = -1;
		SetServiceStatus(Env.hStatus, &Env.serviceStatus);

		LogMessage("skype_plugin: Service is stopped", Env.logFile);
		MyOutputDebugString(TEXT("skype_plugin: ServiceMain() - Service is stopped"));
		return;
	}
	MyOutputDebugString(TEXT("skype_plugin: ServiceMain() - Skype plugin is initialized"));

	// main cycle
	MSG msg;
	while(Env.serviceStatus.dwCurrentState == SERVICE_RUNNING && !Env.bError &&
		GetMessage(&msg, NULL, 0, 0 ))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
		//Sleep(SERVICE_SLEEP);
	}

	// Set stop event in signaled state
	SetEvent(Env.hStopEvent);

	// Wait threads
	WaitForMultipleObjects(COUNT_THREADS, hThreads, TRUE, INFINITE);
	
	// Destroy skype plugin
	DestroySkypePlugin(&Env, hThreads);
	
	MyOutputDebugString(TEXT("skype_plugin: ServiceMain() - Service is stopped"));
	return;
}